﻿namespace NTLib.Core.Manipulators
{
    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Text;
    using System.Threading.Tasks;
    using NTLib.Core.ScopeLock;
    using NTLib.Core.Threading;
    using System.Runtime.CompilerServices;
    using NTLib.Core.Args;
    using Memory;

    /// <summary>
    /// Protect the data access by a lock mecanisme
    /// </summary>
    /// <typeparam name="T"></typeparam>
    /// <typeparam name="Locker">The type of the ocker.</typeparam>
    /// <seealso cref="NTLib.Core.Manipulators.IAccessor{T}" />
    public class ThreadSafeAccessor<T, Locker> : Disposable, IAccessor<T>
        where Locker : ILocker, new()
    {
        #region Fields

        private readonly Locker _lock;
        private readonly bool _disposeContent;
        private T _fields;

        #endregion

        #region CTOR

        /// <summary>
        /// Initializes a new instance of the <see cref="ThreadSafeAccessorBase{T, Locker}" /> class.
        /// </summary>
        /// <param name="locker">The locker.</param>
        /// <param name="disposeContent">if set to <c>true</c> [dispose content].</param>
        public ThreadSafeAccessor(Locker locker = default(Locker), bool disposeContent = false)
        {
            this._lock = locker;
            if (object.Equals(locker, default(Locker)))
                this._lock = new Locker();

            this._disposeContent = disposeContent;
        }

        #endregion

        #region Properties

        /// <summary>
        /// Gets a value indicating whether this instance can set a value.
        /// </summary>
        /// <value>
        ///   <c>true</c> if this instance can set; otherwise, <c>false</c>.
        /// </value>
        /// [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public bool CanSet => true;

        /// <summary>
        /// Gets the type of the content.
        /// </summary>
        /// [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public Type ContentType => typeof(T);

        #endregion

        #region Event

        /// <summary>
        /// Occurs when the value changed.
        /// </summary>
        public event Action<ValueChangedEventArgs<T>> ValueChanged;

        #endregion

        #region Methods

        /// <summary>
        /// Gets the value.
        /// </summary>
        public T Get()
        {
            T copy = default(T);
            using (var scp = GetScoped())
            {
                copy = this._fields;
            }
            return copy;
        }

        /// <summary>
        /// Gets the scoped.
        /// </summary>
        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public ScopeLockItem<T> GetScoped()
        {
            this._lock.Lock(true);
            return new ScopeLockItem<T>(this._fields, (obj) =>
            {
                this._lock.Release(true);
            });
        }

        /// <summary>
        /// Sets the specified value.
        /// </summary>
        /// <param name="value">The value.</param>
        /// <param name="force"></param>
        /// <returns>
        /// return <c>true</c> if the value have been changed; otherwise <c>false</c>
        /// </returns>
        public bool Set(T value, bool force = false)
        {
            using (this._lock.ScopeLock())
            {
                if (force ||
                    !object.ReferenceEquals(this._fields, value) ||
                    !object.Equals(this._fields, value))
                {
                    var old = this._fields;
                    this._fields = value;

                    if (ValueChanged != null)
                        ValueChanged(new ValueChangedEventArgs<T>(old, this._fields));

                    return true;
                }
            }
            return false;
        }

        /// <summary>
        /// Dispose methods called whenever is the dipose origin.
        /// </summary>
        protected override void DisposeEveryTime()
        {
            if (this._lock != null && !this._lock.IsDisposed)
            {
                this._lock.Dispose();
            }

            var field = (this._fields as IDisposable);
            if (field != null && this._disposeContent)
            {
                field.Dispose();
            }

            base.DisposeEveryTime();
        }

        #endregion
    }
}
